package com.young;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
public class IndexController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //高并发下会出现超买，没有加锁
    @GetMapping("/reduct01")
    public String reduct01(){
        Integer stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock<0){
            System.out.println("扣减失败，库存不足");
        }else{
            Integer realStock=stock-1;
            stringRedisTemplate.opsForValue().set("stock",realStock+"");
            System.out.println("扣减成功，剩余库存："+realStock);
        }
        return "end";
    }

    //高并发下单机模式不会出现超买，但是在分布式环境下，仍然会出现超买的现象
    @GetMapping("/reduct02")
    public String reduct02(){
        synchronized (this) {
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock < 0) {
                System.out.println("扣减失败，库存不足");
            } else {
                Integer realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功，剩余库存：" + realStock);
            }
        }
        return "end";
    }

    //通过setnx key value命令，对商品库存进行加锁
    @GetMapping("/reduct03")
    public String reduct03(){

        //这里的key一般设置为和商品有关的key，以提高性能
        String lockKey="product_01";

        //这里可以将value设置为线程id
        String value=UUID.randomUUID().toString();

//        stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"setnx"); -> setnx key value
//        stringRedisTemplate.expire(lockKey,10,TimeUnit.SECONDS);

        //设置setnx和超时时间要原子操作
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value, 10, TimeUnit.SECONDS);

        if (!flag){
            //加锁失败
            return "error code";
        }

        /**
         * 因为在操作的过程中，可能会抛出异常，因此我们要用try将代码块包起来，然后在finally中，释放锁，否则会出现因为异常退出，但锁在redis中存在，从而
         * 使其他线程在访问该商品时加锁失败，无法顺利扣减库存
         */
        try {
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock < 0) {
                System.out.println("扣减失败，库存不足");
            } else {
                Integer realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功，剩余库存：" + realStock);
            }
        }finally {
            //双重保证，将value设置为当前线程的id，保证锁不会被其他线程删掉
            if (value.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                //删除锁
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "end";
    }

    /**
     * 在上面的reduct03代码中，我们通过setnx key value进行加锁，我们设置的超时时间是10秒，假设当前商品库存为100，现在线程A访问该方法，获取到
     * 锁，然后在执行业务代码时，执行的时间超过了10秒，此时锁过期了，这时线程B访问该方法，获取到锁，获取商品库存为100，这样当A和B线程都执行完毕后，
     * 商品的库存实际上，只减少了1，也就是变成99，从而导致超买,那么我们需要在锁过期前，进行续期，因此使用redisson
     * @return
     */
    @GetMapping("/reduct04")
    public String reduct04(){
        String lockKey="product_01";

        RLock redissonLock = redisson.getLock(lockKey);

        try {
            redissonLock.lock();
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock < 0) {
                System.out.println("扣减失败，库存不足");
            } else {
                Integer realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功，剩余库存：" + realStock);
            }
        }finally {
            redissonLock.unlock();
            //删除锁
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }

    /**
     * 考虑一种情况，我们使用redis集群，当设置key的时候，主节点甲会异步地将数据同步到其他从节点，假设现在线程A扣减商品product1库存，那么它在主节点A中设置可以，
     * 但是假设主节点A在同步数据之前挂了，数据没同步到从节点，现在从节点乙推选为主节点，线程B访问主节点乙，然后也是访问商品product1，设置key值，然后假设A、B线
     * 程同时结束，这个时候，会出现超买问题。根本原因是Redis主从同步是异步的，我们可以使用zookeeper来加分布式锁，zookeeper在主从同步时，主节点设置成功后，会
     * 先同步给从节点，只有集群中有一半以上同步后，才返回true，而即使主节点挂了，在推选主节点的时候，选择的也会是数据最完善的那个，因此不会出现刚才的问题，但带来
     * 的问题就是，zookeeper的性能比redis差
     */

//    @RequestMapping("/deduct_stock")
//    public String deductStock(){
        //锁住的lock要以资源为名，提高效率
//        String lockKey="product_001";
//
//        //可以设置为线程id
//        String clientId= UUID.randomUUID().toString();
//
//        //jedis.setnx(key,value)
////        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");
////        stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
//
//        //加锁和设置过时时间要原子操作,考虑问题，执行时间大于超时时间?
//        Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10,TimeUnit.SECONDS);
//
//        if (!result){
//            return "error_code";
//        }
//
//        try{
//            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock");
//            if (stock>0){
//                int realStock=stock-1;
//                stringRedisTemplate.opsForValue().set("stock",realStock+"");
//                System.out.println("扣减成功，剩余库存："+realStock);
//            }else{
//                System.out.println("扣减失败，库存不足");
//            }
//        }finally {
//            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
//                //当前线程的id和取出来的id相同时，才解锁
//                stringRedisTemplate.delete(lockKey);
//            }
//        }
//        return "end";
//
//        String lockKey="product_001";
//
//        RLock redissonLock = redisson.getLock(lockKey);
//
//        try{
//            redissonLock.lock();
//            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
//            if (stock>10){
//                int realStock=stock-1;
//                stringRedisTemplate.opsForValue().set("stock",realStock+"");
//                System.out.println("扣减成功，剩余库存："+realStock);
//            }else{
//                System.out.println("扣减失败，库存不足");
//            }
//        }finally {
//            redissonLock.unlock();
//        }
//        return "end";
//    }
}
